#!/usr/bin/env bash

set -E

file_is_not_empty() {
  local filename="$1"
  local line_count="$(wc -l "$filename" 2>/dev/null || true)"

  if [ -n "$line_count" ]; then
    words=( $line_count )
    [ "${words[0]}" -gt 0 ]
  else
    return 1
  fi
}

update_failed() {
  { echo
    echo "UPDATE FAILED"
    echo

    if ! rmdir "${DOWNLOAD_PATH}" 2>/dev/null; then
      echo "Inspect or clean up the working tree at ${DOWNLOAD_PATH}"

      if file_is_not_empty "$LOG_PATH"; then
        echo "Results logged to ${LOG_PATH}"
        echo
        echo "Last 10 log lines:"
        tail -n 10 "$LOG_PATH"
      fi
    fi
  } >&2
  exit 1
}

compute_sha2() {
  local output
  if type shasum &>/dev/null; then
    output="$(shasum -a 256 -b)" || return 1
    echo "${output% *}"
  elif type openssl &>/dev/null; then
    output="$(openssl dgst -sha256)" || return 1
    echo "${output##* }"
  elif type sha256sum &>/dev/null; then
    output="$(sha256sum --quiet)" || return 1
    echo "${output% *}"
  else
    return 1
  fi
}

compute_md5() {
  local output
  if type md5 &>/dev/null; then
    md5 -q
  elif type openssl &>/dev/null; then
    output="$(openssl md5)" || return 1
    echo "${output##* }"
  elif type md5sum &>/dev/null; then
    output="$(md5sum -b)" || return 1
    echo "${output% *}"
  else
    return 1
  fi
}

verify_checksum() {
  local checksum_command="compute_sha2"
  # If the specified filename doesn't exist, return success
  local filename="$1"
  [ -e "$filename" ] || return 0

  # If there's no expected checksum, return success
  local expected_checksum=`echo "$2" | tr [A-Z] [a-z]`
  [ -n "$expected_checksum" ] || return 0

  # If the checksum length is 32 chars, assume MD5, otherwise SHA2
  if [ "${#expected_checksum}" -eq 32 ]; then
    checksum_command="compute_md5"
  fi

  # If the computed checksum is empty, return failure
  local computed_checksum=`echo "$($checksum_command < "$filename")" | tr [A-Z] [a-z]`
  [ -n "$computed_checksum" ] || return 1

  if [ "$expected_checksum" != "$computed_checksum" ]; then
    { echo
      echo "checksum mismatch: ${filename} (file is corrupt)"
      echo "expected $expected_checksum, got $computed_checksum"
      echo
    } >&4
    return 1
  fi
}

http() {
  local method="$1"
  local url="$2"
  local file="$3"
  [ -n "$url" ] || return 1

  if type curl &>/dev/null; then
    "http_${method}_curl" "$url" "$file"
  elif type wget &>/dev/null; then
    "http_${method}_wget" "$url" "$file"
  else
    echo "error: please install \`curl\` or \`wget\` and try again" >&2
    exit 1
  fi
}

http_head_curl() {
  curl -qsILf "$1" >&4 2>&1
}

http_get_curl() {
  curl -q -o "${2:--}" -sSLf "$1"
}

http_head_wget() {
  wget -q --spider "$1" >&4 2>&1
}

http_get_wget() {
  wget -nv -O "${2:--}" "$1"
}

fetch_version_file() {
  local file_name="$1"
  local file_url="$2"
  local mirror_url

  local version_filename=$(basename $file_url)
  if [ -n "$MIRROR_URL" ]; then
    mirror_url="${MIRROR_URL}/$version_filename"
  fi

  echo "Downloading $version_filename..." >&2
  http head "$mirror_url" &&
  download_file "$mirror_url" "$file_name" ||
  download_file "$file_url" "$file_name"
}

fetch_rpm() {
  local package_name="$1"
  local package_url="$2"
  local mirror_url
  local checksum

  if [ "$package_url" != "${package_url/\#}" ]; then
    checksum="${package_url#*#}"
    package_url="${package_url%%#*}"

    if [ -n "$MIRROR_URL" ]; then
      mirror_url="${MIRROR_URL}/$(basename $package_url)"
    fi
  fi

  echo "Downloading ${package_name}..." >&2
  http head "$mirror_url" &&
  download_file "$mirror_url" "$package_name" "$checksum" ||
  download_file "$package_url" "$package_name" "$checksum"
}

download_file() {
  local package_url="$1"
  [ -n "$package_url" ] || return 1

  local package_filename="$2"
  local checksum="$3"

  echo "-> $package_url" >&2

  if http get "$package_url" "$package_filename" >&4 2>&1; then
    verify_checksum "$package_filename" "$checksum" >&4 2>&1 || return 1
  else
    echo "error: failed to download $package_filename" >&2
    return 1
  fi
}

upgrade() {
  pushd "$DOWNLOAD_PATH" >&4
  package_url=$(head -n 1 $VERSION_FILENAME)
  package_info=$(basename $package_url)
  package_name="${package_info%#*}"
  fetch_rpm $package_name $package_url
  rpm -Uvh $package_name ||
  { echo "error: failed to rpm upgrade."
    exit 1
  } >&2
  popd >&4
}

check() {
  local current_package="$(rpm -q flexgw)"
  [ -n "$current_package" ] ||
  { echo "error: failed to get installed flexgw package info."
    exit 1
  } >&2

  pushd "$DOWNLOAD_PATH" >&4
  fetch_version_file "$VERSION_FILENAME" "$MASTER_URL/$VERSION_FILENAME" || update_failed
  package_url=$(head -n 1 $VERSION_FILENAME)
  popd >&4

  package_name=$(basename $package_url)
  version="${package_name%.rpm*}"
  if [[ "x$version" > "x$current_package" ]]; then
    echo "Found new version: $version"
    return 1
  else
    echo "Already latest version."
    return 0
  fi
}

usage() {
  { echo "usage: update [-y|--yes] [-c|--check]"
    echo "       update --check"
  } >&2

  if [ -z "$1" ]; then
    exit 1
  fi
}

if [ -z "$TMPDIR" ]; then
  TMP="/tmp"
else
  TMP="${TMPDIR%/}"
fi

if [ ! -w "$TMP" ] || [ ! -x "$TMP" ]; then
  echo "update: TMPDIR=$TMP is set to a non-accessible location" >&2
  exit 1
fi

if [ -z "$MASTER_URL" ]; then
  MASTER_URL="http://mirrors.aliyun.com/sre/flexgw"
else
  MASTER_URL="${MASTER_URL%/}"
fi

if [ -z "$MIRROR_URL" ]; then
  MIRROR_URL="http://mirrors.aliyuncs.com/sre/flexgw"
else
  MIRROR_URL="${MIRROR_URL%/}"
fi

SEED="$(date "+%Y%m%d%H%M%S").$$"
DOWNLOAD_PATH="${TMP}/flexgw-update.${SEED}"
LOG_PATH="${TMP}/flexgw-update.${SEED}.log"

exec 4<> "$LOG_PATH" # open the log file at fd 4

trap update_failed ERR
mkdir -p "$DOWNLOAD_PATH"
VERSION_FILENAME="latest.txt"
command="$1"
case "$command" in
"" | "-h" | "--help" )
    usage without_exiting >&2
    ;;
"-y" | "--yes" )
    check || upgrade
    ;;
"-c" | "--check" )
    check || true
esac
e_c="$?"
[ -z "${KEEP_DOWNLOAD_PATH}" ] && rm -fr "$DOWNLOAD_PATH"
exit $e_c
trap - ERR

